package framework.webmvc.servlet;

import framework.annotation.InchController;
import framework.annotation.InchRequestMapping;
import framework.context.InchApplicationContext;
import framework.webmvc.component.*;
import lombok.extern.slf4j.Slf4j;
import util.OSCheckAndConvert;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Starting class
 */
@Slf4j
public class InchDispatcherServlet extends HttpServlet {
    //configuration file position  key value in the web.xml
    private final String LOCATION = "contextConfigLocation";

    //the list for handler
    private List<InchHandler> handlerList = new ArrayList<InchHandler>();
    //
    private Map<InchHandler, InchHandlerAdapter> handlerAdapterMap= new HashMap<InchHandler,InchHandlerAdapter>();
    // store the folders that contains view files
    private List<InchViewResolver> viewResolvers = new ArrayList<InchViewResolver>();

    // instance of InchApplicationContext
    private InchApplicationContext context;

    /**
     * initial method
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //initial IoC container
        context = new InchApplicationContext(config.getInitParameter(LOCATION));
        initStrategies(context);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req,resp);
    }

    /**
     * main method to deal with the request
     * @param request httpservletrequest
     * @param response httpservletrespons
     */
    private void doDispatch(HttpServletRequest request,HttpServletResponse response) {
        //get a handler by request
        InchHandler handler = getHandler(request);
        //if handler is not found
        if(handler == null) {
            //show error page
            processDispatchResult(request,response,new InchModelAndView("404"));
            return;
        }
        //get handler adapter for this handler
        InchHandlerAdapter handlerAdapter = getHandlerAdaptor(handler);
        //use handler adapter to get output model and view
        InchModelAndView modelAndView = handlerAdapter.handle(request,response,handler);
        //show the view
        processDispatchResult(request,response,modelAndView);

    }


    /**
     * get a handler adapter for a handler
     * @param handler handler that need a adapter
     * @return an adapter for this handler
     */
    private InchHandlerAdapter getHandlerAdaptor(InchHandler handler) {
        if(this.handlerAdapterMap.isEmpty()) { return null; }
        InchHandlerAdapter handlerAdapter = handlerAdapterMap.get(handler);
        if(handlerAdapter.supports(handler)) { return handlerAdapter; }
        return null;
    }

    /**
     * show the result of the request
     * @param request
     * @param response
     * @param modelAndView the Model and View it should show to user
     */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, InchModelAndView modelAndView) {
        //check null
        if(modelAndView == null) { return; }
        if(this.viewResolvers == null) { return; }
        //check if there no view in viewResolvers
        if(this.viewResolvers.isEmpty()) { return; }
        //loop all the viewResolvers and find the view then show it with the model
        for(InchViewResolver viewResolver : viewResolvers) {
            InchView view = viewResolver.getViewByName(modelAndView.getViewName(),null);
            if(view != null) {
                try {
                    view.render(request,response,modelAndView.getModel());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return;
            }
        }
    }




    /**
     * get the request method and find the handler in handler list
     * @param request HttpServletRequest
     * @return the handler that handle the request method
     */
    private InchHandler getHandler(HttpServletRequest request) {
        // if no handler are in the list
        if(this.handlerList.isEmpty()) { return null; }
        //get url and try find if there is a match in handler map eg: url= "/query"
        String url =request.getRequestURI();
        String contextPath  = request.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+", "/");
        url = OSCheckAndConvert.stringConvert(url);
        //loop handler list to find the needed handler
        for(InchHandler handler : this.handlerList) {
            Matcher matcher = handler.getPattern().matcher(url);
            if(matcher.find()) { return handler; }
        }
        // if can't find return null
        return null;
    }


    /**
     * initial the 9 main components
     * @param context
     */
    protected void initStrategies(InchApplicationContext context) {
        //********************************9 main components is here********************************

        /**
         * not fulfilled
         *
        //analyze the file that upload, if the file is multipart then use MultipartResolver
        initMultipartResolver(context);
        //locale resolver
        initLocaleResolver(context);
        //locale theme resolver
        initThemeResolver(context);
         */
        //initial handlers
        initHandlerMappings(context);
        //initial handler adapters
        initHandlerAdapters(context);

        /**
         * not fulfilled
         *
        //deal with the exceptions for handler
        initHandlerExceptionResolvers(context);
        //get view name from request
        initRequestToViewNameTranslator(context);
         */


        //initial view
        initViewResolver(context);

        /**
         * not fulfilled
         *
        //Flash Map
        initFlashMapManager(context);
         */
        //********************************9 main components is here********************************
    }


    /**
     * initial the handlers and put them into Map(we use list here)
     * @param context
     */
    private void initHandlerMappings(InchApplicationContext context) {
        //get all the bean names
        String[] beanNames = context.getBeanDefinitionNames();
        try {
            //loop each bean
            for(String beanName : beanNames) {
                Object controller = context.getBean(beanName);
                Class<?> clazz = controller.getClass();
                //check if it is controller bean
                if(!clazz.isAnnotationPresent(InchController.class)) { continue; }
                String baseUrl = "";
                //get the method name eg: "/demo"
                if(clazz.isAnnotationPresent(InchRequestMapping.class)) {
                    InchRequestMapping requestMapping = clazz.getAnnotation(InchRequestMapping.class);
                    //the name of the method in handler
                    baseUrl = requestMapping.value();
                }
                Method[] methods = clazz.getMethods();
                for(Method method : methods) {
                    if(!method.isAnnotationPresent(InchRequestMapping.class)) { continue; }
                    InchRequestMapping requestMapping = method.getAnnotation(InchRequestMapping.class);
                    //get name of the method
                    String methodName = requestMapping.value().replaceAll("\\*",".*");
                    //give a regex
                    String regex = ("/" + baseUrl + methodName).replaceAll("/+","/" );
                    Pattern pattern = Pattern.compile(regex);
                    //add handler into map(here we use list)
                    this.handlerList.add(new InchHandler(controller,method,pattern));
                    log.info("Mapping: regex:" + regex + "\tMethod:\t" + methodName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * set adapter to handler (put them in a map)
     * @param context
     */
    private void initHandlerAdapters(InchApplicationContext context) {
        for(InchHandler handler : handlerList) {
            this.handlerAdapterMap.put(handler,new InchHandlerAdapter());
        }
    }


    /**
     * find view files and put into viewResolver list
     * in this project we assume that different folder is different resolver
     * eg: here are the resolvers in this project: "/layout" "/layout/error"
     * @param context
     */
    private void initViewResolver(InchApplicationContext context) {
        //find view resolver root path
        String templateRoot = context.getConfig().getProperty("templateRoot");
        String templateRootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
        templateRootPath = OSCheckAndConvert.stringConvert(templateRootPath);
        File templateRootDir = new File(templateRootPath);
        //search the file/folder and add to the viewResolver list
        searchAndAddViewResolvers(templateRootDir);
    }

    /**
     * search the given file and find all folder(resolver) and put into viewResolverList
     * @param file
     */
    private void searchAndAddViewResolvers(File file){
        //check if it is a resolver(folder)
        if(!file.isDirectory()) { return; }
        this.viewResolvers.add(new InchViewResolver(file));
        for(File f : file.listFiles()) {
            searchAndAddViewResolvers(f);
        }
    }


    //********************************** these functions are not fulfilled************************************
    private void initFlashMapManager(InchApplicationContext context) {}
    private void initRequestToViewNameTranslator(InchApplicationContext context) {}
    private void initHandlerExceptionResolvers(InchApplicationContext context) {}
    private void initThemeResolver(InchApplicationContext context) {}
    private void initLocaleResolver(InchApplicationContext context) {}
    private void initMultipartResolver(InchApplicationContext context) {}
    //********************************** these functions are not fulfilled************************************

}
